package com.blue.fish.tiny.parser.ast;

import com.blue.fish.tiny.lexer.Token;
import com.blue.fish.tiny.parser.util.ExprHOF;
import com.blue.fish.tiny.parser.util.ParseException;
import com.blue.fish.tiny.parser.util.PeekTokenIterator;
import com.blue.fish.tiny.parser.util.PriorityTable;

/**
 * @author QingFan
 * @version 1.0.0
 * @date 2023年01月30日 21:36
 */
public class Expr extends ASTNode {

    private static final PriorityTable table = new PriorityTable();

    public Expr() {
        super();
    }

    public Expr(ASTNode parent) {
        super(parent);
        this.label = lexeme.getValue();
    }

    public Expr(ASTNode parent, ASTNodeTypes type, Token lexeme) {
        super(parent);
        this.type = type;
        this.label = lexeme.getValue();
        this.lexeme = lexeme;
    }

    /**
     * <code>
     * left:E(k) -> E(k) op(k) E(k+1) | E(k+1)
     * right:
     * E(k) -> E(k+1) E_(k)
     * var e = new Expr(); e.left = E(k+1); e.op = op(k); e.right = E(k+1) E_(k)
     * E_(k) -> op(k) E(k+1) E_(k) | ε
     * 最高优先级处理:
     * E(t) -> F E_(k) | U E_(k)
     * E_(t) -> op(t) E(t) E_(t) | ε
     * </code>
     */
    public static ASTNode E(ASTNode parent, int k, PeekTokenIterator it) throws ParseException {
        if (k < table.size() - 1) {
            return combine(parent, it, () -> E(parent, k + 1, it), () -> E_(parent, k, it));
        }
        return race(it,
                () -> combine(parent, it, () -> U(parent, it), () -> E_(parent, k, it)),
                () -> combine(parent, it, () -> F(parent, it), () -> E_(parent, k, it))
        );
    }

    public static ASTNode E_(ASTNode parent, int k, PeekTokenIterator it) throws ParseException {
        var token = it.peek();
        String value = token.getValue();
        if (table.get(k).contains(value)) {
            var expr = new Expr(parent, ASTNodeTypes.BINARY_EXPR, it.nextMatch(value));
            expr.addChild(combine(parent, it, () -> E(parent, k + 1, it), () -> E_(parent, k, it)));
            return expr;
        }
        return null;
    }

    private static ASTNode F(ASTNode parent, PeekTokenIterator it) {
        Token token = it.peek();
        if (token.isVariable()) {
            return new Variable(parent, it);
        }
        return new Scalar(parent, it);
    }

    private static ASTNode U(ASTNode parent, PeekTokenIterator it) throws ParseException {
        var token = it.peek();
        var value = token.getValue();

        if (value.equals("(")) {
            it.nextMatch("(");
            var expr = E(parent, 0, it);
            it.nextMatch(")");
            return expr;
        } else if (value.equals("++") || value.equals("--") || value.equals("!")) {
            var t = it.peek();
            it.nextMatch(value);
            Expr unaryExpr = new Expr(parent, ASTNodeTypes.UNARY_EXPR, t);
            unaryExpr.addChild(E(parent, 0, it));
            return unaryExpr;
        }
        return null;
    }

    private static ASTNode race(PeekTokenIterator it, ExprHOF aFunc, ExprHOF bFunc) throws ParseException {
        if (!it.hasNext()) {
            return null;
        }
        var a = aFunc.hoc();
        if (a != null) {
            return a;
        }
        return bFunc.hoc();
    }

    private static ASTNode combine(ASTNode parent, PeekTokenIterator it, ExprHOF aFunc, ExprHOF bFunc) throws ParseException {
        var a = aFunc.hoc();
        if (a == null) {
            return it.hasNext() ? bFunc.hoc() : null;
        }
        var b = it.hasNext() ? bFunc.hoc() : null;
        if (b == null) {
            return a;
        }
        Expr expr = new Expr(parent, ASTNodeTypes.BINARY_EXPR, b.lexeme);
        expr.addChild(a);
        expr.addChild(b.getChild(0));
        return expr;
    }

    public static ASTNode parse(PeekTokenIterator it) throws ParseException {
        return E(null, 0, it);


    }
}
